#define lib_psync_c
#define LUA_LIB

extern "C" {
#include "../lua.h"
#include "../lauxlib.h"
#include "../lualib.h"

#include "../lj_def.h" // Included always for the typedefs
#include "../lj_lib.h" // Included always for the typedefs
}

#undef cast
#define GVL_EXCLUDE_STDINT 1 // lj2 has type typedefs

#include "psync/node.hpp"
#include "util.hpp"

using namespace Psync5;

/* ------------------------------------------------------------------------ */

LUA_CHECK_FUNC(LuaNode, node, UDTYPE_PSYNC_NODE)

extern "C" {

// <Node methods>

#define LJLIB_MODULE_psync_node_method

LJLIB_CF(psync_node_method___gc)
{
	if(LuaNode* node = static_cast<LuaNode*>(try_node(L, 1)))
	{
		lua_clear_object_tag(L, 1);
		node->~LuaNode();
	}

	return 0;
}

// <Library functions>

#define LJLIB_MODULE_psync

LJLIB_PUSH(top-2) LJLIB_SET(!) // Psync node metatable

LJLIB_CF(psync_create_node_ud)
{
	void* mem = lua_create_object(L, UDTYPE_PSYNC_NODE, sizeof(LuaNode));
	LuaNode* self = new (mem) LuaNode();

	return 1;
}

LJLIB_CF(psync_node_host)
{
	auto& node = check_node(L, 1);

	int port = luaL_checkint(L, 2);
	node.host(port);

	return 1;
}

LJLIB_CF(psync_node_connect)
{
	auto& node = check_node(L, 1);

	char const* addr = luaL_checkstring(L, 2);
	int port = luaL_checkint(L, 3);

	lua_pushinteger(L, node.connect(addr, port)->id);

	return 1;
}

LJLIB_CF(psync_node_disconnect)
{
	auto& node = check_node(L, 1);

	for(auto r = node.allLinks(); !r.empty(); r.pop_front())
	{
		r.front().disconnect();
	}

	return 0;
}

LJLIB_CF(psync_node_link_latency)
{
	auto& node = check_node(L, 1);

	Link* link = node.linkFromId(luaL_checkint(L, 2));
	if(!link)
		luaL_error(L, "invalid link");

	lua_pushnumber(L, link->calcLatency());
	return 1;
}

LJLIB_CF(psync_node_accept_connections)
{
	auto& node = check_node(L, 1);

	bool accept = lua_toboolean(L, 2) != 0;

	node.acceptConnections(accept);
	return 0;
}

LJLIB_CF(psync_node_auto_send_packets)
{
	auto& node = check_node(L, 1);

	bool f = lua_toboolean(L, 2) != 0;

	node.automaticPacketSending(f);
	return 0;
}

LJLIB_CF(psync_node_send_packet_all)
{
	auto& node = check_node(L, 1);

	for(auto r = node.allLinks(); !r.empty(); r.pop_front())
	{
		r.front().assembleAndSendPacket();
	}

	return 0;
}

LJLIB_CF(psync_node_extract_serializer)
{
	auto& node = check_node(L, 1);

	LuaLink* link = static_cast<LuaLink*>(node.linkFromId(luaL_checkint(L, 2)));

	if(!link)
		luaL_error(L, "invalid link");

	if(link->serialize)
		luaL_error(L, "serializer was already extracted");

	link->createContext(L);
	return 1;
}

LJLIB_CF(psync_node_process)
{
	auto& node = check_node(L, 1);

	node.process();

	return 0;
}

#if 0
LUAF(node_link_send_packet)
{
	auto& node = check_node(L, 1);

	Link* link = node.linkFromId(luaL_checkint(L, 2));
	if(!link) luaL_error(L, "invalid link");

	link->assembleAndSendPacket();

	return 0;
}

LUAF(node_link_time_for_packet)
{
	auto& node = check_node(L, 1);

	Link* link = node.linkFromId(luaL_checkint(L, 2));
	if(!link) luaL_error(L, "invalid link");

	lua_pushboolean(L, link->isTimeForPacket());

	return 1;
}
#endif

LJLIB_CF(psync_node_next_event)
{
	auto& node = check_node(L, 1);

	if(node.events.empty())
		return 0;

	LuaNodeEvent& ev = node.events.front();

	if(ev.type == LuaNodeEvent::Connect)
	{
		lua_pushstring(L, "link connect");
		lua_pushinteger(L, ev.linkId);
	}
	else if(ev.type == LuaNodeEvent::Disconnect)
	{
		lua_pushstring(L, "link disconnect");
		lua_pushinteger(L, ev.linkId);
	}
	else if(ev.type == LuaNodeEvent::LinkCreated)
	{
		lua_pushstring(L, "link created");
		lua_pushinteger(L, ev.linkId);
	}
	node.events.pop();
	return 2;
}

static void overflow(bytewriter* w, int b)
{
	LuaLink* link = static_cast<LuaLink*>(w->user);

	if(link->nextSection)
	{
		// Correct size of section
		link->nextSection->sizeBytes = int(w->next - reinterpret_cast<unsigned char const*>(link->nextSection->buffer));
		link->nextSection->messageEnds = w->message_ends;
		link->nextSection->partial = (b >= 0); // Partial if it overflowed due to an attempted write

		passert(!link->nextSection->partial || link->nextSection->sizeBytes == Section::MaxBufByteSize,
			"Partial section should be of maximum size");

		if(link->nextSection->sizeBytes != 0)
		{
			// Queue nextSection
			link->channel().queue(link->nextSection.release());
		}
	}

	w->message_ends = 0;

	if(!link->nextSection || link->nextSection->sizeBytes > 0)
	{
		// Prepare a new section
		link->nextSection.reset(link->channel().newSendSection());

		w->next = reinterpret_cast<unsigned char*>(link->nextSection->buffer);
		w->end = reinterpret_cast<unsigned char*>(link->nextSection->buffer + Section::MaxBufByteSize);
	}

	if(b >= 0)
		*w->next++ = b;
}

static int underflow(bytereader* r)
{
	LuaLink* link = static_cast<LuaLink*>(r->user);

	Psync5::Channel& channel = link->channel();

	if(!link->recvQueue.empty())
	{
		link->recvSection.reset(link->recvQueue.first());
		link->recvQueue.unlink_front();

		Psync5::Section& recvSection = *link->recvSection;

		int sizeBytes = recvSection.sizeBytes;

		r->next = reinterpret_cast<unsigned char const*>(recvSection.buffer);

		unsigned char* end = reinterpret_cast<unsigned char*>(recvSection.buffer) + sizeBytes;

		if(!recvSection.partial)
		{
			// Range coder needs to read 4 extra zero bytes and
			// this is the simplest way to accomplish that.
			for(int i = 0; i < 4; ++i)
				*end++ = 0;
		}
	
		r->end = end;

		link->last_message_in_section += recvSection.messageEnds;

		// TODO: We need next_complete_message_end updated even when we're still on a
		// partial section, because we may have read a message completely earlier
		// than expected.
		// When finding a complete message end we also need the serialization context
		// to skip over sections until (and including) a complete section.

		r->next_complete_message_end = (recvSection.partial ?
			 link->next_unreceived_message + 1 : link->last_message_in_section);
		
		return 1;
	}

	return 0;
}

static void update(bytereader* r)
{
	LuaLink* link = static_cast<LuaLink*>(r->user);

	link->updateRecvQueue();

	r->next_unreceived_message = link->next_unreceived_message;
}

static void writer_destroy(bytewriter* w)
{
	LuaLink* link = static_cast<LuaLink*>(w->user);

	// Sever tie
	link->serialize = 0;
}

static void reader_destroy(bytereader* r)
{
	LuaLink* link = static_cast<LuaLink*>(r->user);

	// Sever tie
	link->serialize = 0;
}

void LuaLink::createContext(lua_State* L)
{
	serialize_context* ctx = serialize_create(L);

	ctx->writer.overflow = overflow;
	ctx->writer.destroy = writer_destroy;
	ctx->writer.user = this;

	ctx->reader.underflow = underflow;
	ctx->reader.destroy = reader_destroy;
	ctx->reader.update = update;
	ctx->reader.user = this;

	serialize = ctx;
}


/* ------------------------------------------------------------------------ */

#include "../lj_libdef.h"
#include "../lj_lib.h"

LUALIB_API int luaopen_psync(lua_State *L)
{
	LJ_LIB_REG_(L, NULL, psync_node_method);
	LJ_LIB_REG(L, psync);
	return 1;
}

} // extern "C"
